#ifndef __MMU_H__
#define __MMU_H__

#include "type.h"

typedef unsigned long long PTE;

// proc.h
typedef struct proc_t proc_t;

/*If it's mapped, will return its level0 pte, if not, will create a map for the va and return its level0 pte */

void kvminit(void);
void create_pages_map(pageTable_t pageTable, address_t va, address_t pa, uint64_t size, uint64_t flags);
PTE* get_level0_pte(pageTable_t pageTable, address_t va, int alloc);
void kvminithart(void);
void kvmfree(pageTable_t pageTable);

// from kernel_end to dsram_end, get a freelist (unused memory)
void kinit();
// free one page(4k)
void kfree(void* pa);
// free certain pages()
void kfree_pages(void* pa_start, void* pa_end);
// allocate one page
void* kalloc(void);

// user space alloc
void alloc_proc_mem(proc_t* p);
void uvminit(pageTable_t pageTable, pageTable_t kpageTable, code_t* code, uint64_t size);
void uvmcopy(proc_t* p, proc_t* newp);
void elf_load(proc_t* p, char* path);
void uvmfree(proc_t* p);

// malloc and free in kernel
void* kmalloc(uint64_t size);
void kmfree(void* address);
void* find_first_fit(uint64_t size);
void* merge(void* address);
void* extend_heap(void);
void place(void* goal, uint64_t size);
void kmm_init(void);
address_t kvm_trans(address_t va, pageTable_t pagetable);

int copy_to_kernel(void* dst, void* src, size_t size);
int copy_to_user(void* dst, void* src, size_t size);

#define WSIZE 8
#define DSIZE 16
#define TSIZE 24
#define FSIZE 32
#define ALIGN(size) ((((uint64_t)(size)) + FSIZE - 1) & (~0x7))

#define PUT(address, value) (*(uint64_t*)(address) = (uint64_t)value)
#define GET(address) (*((uint64_t*)address))
#define GET_SIZE(address) ((GET(HEAD(address))) & ~0x7)
#define PACK(size, alloc) ((((uint64_t)(size)) & ~0x7) | (alloc & 0x1))
#define HEAD(address) ((byte_t*)address - TSIZE)
#define NEXT(address) ((byte_t*)address - WSIZE)
#define PREV(address) ((byte_t*)address - DSIZE)
#define NEXT_ADDRESS(address) (GET(NEXT(address)))
#define PREV_ADDRESS(address) (GET(PREV(address)))
#define ALLOC(address) ((GET(HEAD(address))) & 0x1)
#define PREV_ALLOC(address) (ALLOC(PREV_ADDRESS(address)))
#define NEXT_ALLOC(address) (ALLOC(NEXT_ADDRESS(address)))
#define MIN_BLOCK 32

#define PAGE_SIZE 4096
#define PAGE_SHIFT 12

#define PAGE_ROUND_UP(va) (((address_t)(va) + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1))  // 向上对齐4kb
#define PAGE_ROUND_DOWN(va) ((address_t)(va) & ~(PAGE_SIZE - 1))                  //向下对齐4kb

// deal with virtual address
#define VPN_MASK 0x1ff
#define VA_OFFSET_MASK 0xfff
#define VPN_SIZE 9
#define VPN_SHIFT 12

#define VPN(va, level) (((address_t)(va) >> (VPN_SHIFT + (level)*VPN_SIZE)) & VPN_MASK)

// deal with pte (page table entry)
#define PPN_SHIFT 10
#define PTE_V (1L << 0)
#define PTE_R (1L << 1)
#define PTE_W (1L << 2)
#define PTE_X (1L << 3)
#define PTE_U (1L << 4)

#define GET_PPN(pa) (((address_t)(pa) >> VPN_SHIFT) << PPN_SHIFT)  //截取物理地址对应的PPN
#define VA_OFFSET(va) ((address_t)(va)&VA_OFFSET_MASK)
#define GET_PA(va, value) (VA_OFFSET(va) + (((address_t)(value) >> PPN_SHIFT) << VPN_SHIFT))
#define GET_PAGETABLE(pte) ((((address_t)(pte)) >> PPN_SHIFT) << VPN_SHIFT)
#define GET_FLAG(value) (((address_t)(value)) & 0x3ff)

// satp
#define SATP_MODE (8L << 60)
#define PAGE_TABLE_SHIFT(pa) (SATP_MODE | (((address_t)(pa)) >> VPN_SHIFT))

// user vm

// elf

typedef struct elf_head {
    uint32_t magic;
    byte_t elf[12];
    uint16_t type;
    uint16_t machine;
    uint32_t version;
    uint64_t entry;
    uint64_t phoff;
    uint64_t shoff;
    uint32_t flags;
    uint16_t ehsize;
    uint16_t phentsize;
    uint16_t phnum;
    uint16_t shentsize;
    uint16_t shnum;
    uint16_t shstrndx;
} elf_head_t;

typedef struct pro_head {
    uint32_t type;
    uint32_t flags;
    uint64_t off;
    uint64_t vaddr;
    uint64_t paddr;
    uint64_t filesz;
    uint64_t memsz;
    uint64_t align;
} pro_head_t;

#define ELF_MAGIC 0x464c457f  //磁盘上确实是0x7f454c46，但是由于磁盘按小端序存储，所以读出结果应为 0x464c457f //" ELF"
#define ELF_PROG_LOAD 1

// user process
#define PRO_SIZE 5 * PAGE_SIZE

#endif